/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/21 19:20
  *
  * @file       pd_i2c_sim.c
  * @brief      I2C simulate driver.
  *
  * @note       This file contains the simulate driver for the I2C.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/21     Chris        the first version
  *
  ******************************************************************************
  */


#include <delay.h>
#include "pd_i2c_sim.h"

static void I2CSimulate_writeBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, FlagStatus BitVal);

#define SCL(x)      I2CSimulate_writeBit(this->i2c.sck->GPIOx, this->i2c.sck->Pin, x)
#define SDA(x)      I2CSimulate_writeBit(this->i2c.sda->GPIOx, this->i2c.sda->Pin, x)
#define SDA_OUT()   GPIO_setOutMode(this->i2c.sda)
#define SDA_IN()    GPIO_setInMode(this->i2c.sda)
#define SDA_GET()   GPIO_get(this->i2c.sda)

static void I2CSimulate_writeBit(GPIO_TypeDef *GPIOx, const uint16_t GPIO_Pin, const FlagStatus BitVal) {
    delay_us(1);
    GPIOx->BSRR = BitVal ? GPIO_Pin : GPIO_Pin << 16;
}

static void I2CSimulate_start(const I2CSimulate *this) {
    SDA_OUT();

    SCL(1);
    SDA(0);
    SDA(1);
    SDA(0);
    SCL(0);
}

static void I2CSimulate_stop(const I2CSimulate *this) {
    SDA_OUT();

    SCL(0);
    SDA(0);
    SCL(1);
    SDA(1);
}

static void I2CSimulate_sendAck(const I2CSimulate *this, const unsigned char ack) {
    SDA_OUT();

    SCL(0);
    SDA(0);

    if (!ack)
        SDA(0);
    else
        SDA(1);

    SCL(1);
    SCL(0);
    SDA(1);
}

static uint8_t I2CSimulate_waitAck(const I2CSimulate *this) {
    uint8_t ack_flag = 10;

    SCL(0);
    SDA(1);
    SDA_IN();

    SCL(1);
    while ((SDA_GET() == 1) && (ack_flag)) {
        ack_flag--;
    }

    if (ack_flag <= 0) {
        I2CSimulate_stop(this);
        return 1;
    } else {
        SCL(0);
        SDA_OUT();
    }
    return 0;
}

static void I2CSimulate_sendByte(const I2CSimulate *this, uint8_t byte) {
    SDA_OUT();
    SCL(0);

    for (uint8_t i = 0; i < 8; i++) {
        SDA((byte & 0x80) >> 7);
        SCL(1);
        SCL(0);
        byte <<= 1;
    }
}

static uint8_t I2CSimulate_readByte(const I2CSimulate *this) {
    unsigned char receive = 0;

    SDA_IN();
    for (int i = 0; i < 8; i++) {
        SCL(0);
        SCL(1);
        receive <<= 1;
        if (SDA_GET()) {
            receive |= 1;
        }
    }
    SCL(0);
    return receive;
}

static void I2CSimulate_init_(const I2C *super) {
    const I2CSimulate *this = (const I2CSimulate *)super;
    GPIO_init(this->i2c.sck);
    GPIO_init(this->i2c.sda);

    GPIO_set(this->i2c.sck);
    GPIO_set(this->i2c.sda);
}

static uint8_t I2CSimulate_send_(const I2C *super, const uint8_t salveAddress, const uint16_t regAddress,
                                 uint8_t *dat, const uint8_t len) {
    const I2CSimulate *this = (const I2CSimulate *)super;

    // I2C start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 0);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 1;
    }

    // I2C send register address that wait to write
    if ((regAddress & 0xFF00) == 0) {
        I2CSimulate_sendByte(this, regAddress);
    } else {
        I2CSimulate_sendByte(this, regAddress >> 8);
        delay_us(1);
        I2CSimulate_sendByte(this, regAddress);
    }
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 2;
    }

    // I2C send data
    for (int i = 0; i < len; ++i) {
        I2CSimulate_sendByte(this, dat[i]);
        if (I2CSimulate_waitAck(this) == 1) {
            I2CSimulate_stop(this);
            return 3 + i;
        }
    }

    // I2C stop
    I2CSimulate_stop(this);

    return 0;
}

static uint8_t I2CSimulate_read_(const I2C *super, const uint8_t salveAddress, const uint16_t regAddress,
                                 uint8_t *dat, const uint8_t len) {
    const I2CSimulate *this = (const I2CSimulate *)super;

    uint8_t i;

    // I2C start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 0);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 1;
    }

    // I2C send register address that wait to read
    if ((regAddress & 0xFF00) == 0) {
        I2CSimulate_sendByte(this, regAddress);
    } else {
        I2CSimulate_sendByte(this, regAddress >> 8);
        delay_us(1);
        I2CSimulate_sendByte(this, regAddress);
    }
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 2;
    }

    // I2C start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 1);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 3;
    }

    // I2C read (len-1) byte, and send ack(0) after every times
    for (i = 0; i < len - 1; ++i) {
        dat[i] = I2CSimulate_readByte(this);
        I2CSimulate_sendAck(this, 0);
    }
    // I2C read the last byte, and send ack(1)
    dat[i] = I2CSimulate_readByte(this);
    I2CSimulate_sendAck(this, 1);
    // I2C stop
    I2CSimulate_stop(this);

    return 0;
}

void I2CSimulate_constructor(I2CSimulate *this, GPIO *sck, GPIO *sda) {
    static struct I2CVtbl vtbl = {
        .init = &I2CSimulate_init_,
        .send = &I2CSimulate_send_,
        .read = &I2CSimulate_read_,
    };
    I2C_constructor(&this->i2c, sck, sda);
    this->i2c.vptr = &vtbl;
}
